﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Component.Mesh;
using Component.Passenger;
using Component.Tools;
using Component.Track;
using Component.UI;
using Config;
using Tools;
using UnityEditor.Experimental.GraphView;
using UnityEngine;

namespace Component.Trains
{
    public class TrainNode : MonoBehaviour 
    {
        private DrawMeshTools meshTools;
        private LineData followLine;
        private Color color;
        private bool reverse;
        private bool outOfLine;
        private int followIndex;
        private int outOfLineIndex;
        private Ray lineRay;
        private Ray stationRay;
        private PassengerNode[] models;

        [SerializeField] private float width;
        [SerializeField] private float height;

        public Task RunTask { get; set; }

        private Task passengerOff;
        private Task passengerOn;
        
        public bool Running => RunTask.Running;
        public bool Paused => RunTask.Paused;


        public void Pause()
        {
            RunTask.Pause();
            passengerOff?.Pause();
            passengerOn?.Pause();
        }
    
        public void Continue()
        {
            RunTask.Continue();
            passengerOff?.Continue();
            passengerOn?.Continue();
        }
    
        public void Stop()
        {
            RunTask.Stop();
        }
        
        public LineData FollowLine
        {
            get => followLine;
            set => followLine = value;
        }

        public Color Color
        {
            get => color;
            set => color = value;
        }

        private void Awake()
        {
            meshTools = gameObject.AddComponent<DrawMeshTools>();
            meshTools.SortLayer = 1;

            FollowLine = new LineData();
        }

        private void Start()
        {
            gameObject.AddComponent<BoxCollider2D>();
            
            // NodeGroup.FormatNodes(transform.GetChild(0), 3, 0.38f, 0.33f);
            
            models = GetComponentsInChildren<PassengerNode>();
            foreach (var t in models)
            {
                t.transform.parent = transform;
                t.gameObject.SetActive(false);
            }
        }

        void Update()
        {
            meshTools.DrawTrain(transform.position, width, height, color);
        }

        private Vector2[] points = new Vector2[0];
        /// <summary>
        /// 【LineData已赋值】当新的线建立会随着自动创建一辆火车
        /// </summary>
        /// <param name="startDir">起始方向</param>
        /// <returns></returns>
        public IEnumerator MoveTask(Vector2 startDir)
        {
            OnLineUpdate();
            if (startDir.x < 0)
            {
                transform.rotation = Quaternion.Euler(0, 0, 180);
            }
            else if(startDir.y > 0)
            {
                transform.rotation = Quaternion.Euler(0, 0, 90);
            }else if (startDir.y < 0)
            {
                transform.rotation = Quaternion.Euler(0, 0, -90);
            }
            
            followIndex = 0;
            transform.position = points[followIndex];

            while (true)
            {
                FollowTask();
                yield return Time.deltaTime;
            }
        }
        /// <summary>
        /// 【LineData已赋值】当新的线建立会随着自动创建一辆火车
        /// </summary>
        /// <param name="startDir">起始方向</param>
        /// <returns></returns>
        public IEnumerator MoveTask(Quaternion rotation, Vector2 startPos)
        {
            Array.Resize(ref points, followLine.Points.Count);
            Array.ConstrainedCopy(followLine.Points.ToArray(), 0, points, 0, followLine.Points.Count);
            float minDistance = float.MaxValue;
            Vector3 direction = Vector3.zero;
            for (int i = 0; i < followLine.Points.Count; i++)
            {
                direction = (followLine.Points[i] - (Vector2)transform.position).normalized;
                //转int 不然transform.right.x为0的时候 会输出为 -1.192093E-07
                if (direction.x * (int)transform.right.x >= 0 && direction.y * (int)transform.right.y >= 0)
                {
                    float distance = Vector2.Distance(startPos, followLine.Points[i]);
                    if (distance < minDistance)
                    {
                        minDistance = distance;
                        followIndex = i;
                        print(followIndex);
                    }
                }
            }
            if (followIndex + 1 < followLine.Points.Count)
            {
                Vector3 d = followLine.Points[followIndex + 1] - (Vector2) transform.position;
                if (d.x * transform.right.x < 0 || d.y * transform.right.y < 0)
                {
                    reverse = true;
                }
                
            }
             
            
            //init transform
            transform.rotation = rotation;
            if (Math.Abs(Mathf.Abs(rotation.eulerAngles.z) % 90) > Mathf.Epsilon)
            {
                transform.position = new Vector3(transform.position.x,
                    (direction.x * direction.y > 0 ? 1 : -1) * (transform.position.x - followLine.Points[followIndex].x) + followLine.Points[followIndex].y);
            }else if (PointTools.VerticalPoint(startPos, followLine.Points[followIndex]))
            {
                transform.position = new Vector3(followLine.Points[followIndex].x, transform.position.y);
            }
            else
            {
                transform.position = new Vector3(transform.position.x, followLine.Points[followIndex].y);
            }
            
            while (true)
            {
                
                if (followIndex > points.Length - 1 || followIndex < 0)
                {
                    Debug.LogError("followIndex is out of range" + " : " + followIndex);
                    yield return null;
                }

                FollowTask();
                Debug.DrawLine(transform.position, points[followIndex]);
                yield return Time.deltaTime;
            }
            
        }
        
        private void FollowTask()
        {
            
            OnLineUpdate();
            
            float distance = Vector2.Distance(transform.position, points[followIndex]);
            if (distance < .06f)
            {
                if (reverse)
                {
                    if (followIndex <= 0)
                    {
                        reverse = !reverse;
                    }
                }
                else
                {
                    if (followIndex >= points.Length - 1)
                    {
                        reverse = !reverse;
                    }
                }
                
                followIndex = reverse ? followIndex - 1 : followIndex + 1;

                float angle = Mathf.Atan2(points[followIndex].y - transform.position.y, 
                    points[followIndex].x - transform.position.x) * Mathf.Rad2Deg;
                
                transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
                transform.Translate(transform.right * Time.deltaTime, Space.World);
            }
            else
            {
                CheckStation();
                transform.Translate(transform.right * (Time.deltaTime * 2), Space.World);
            }
        }

        private void OnLineUpdate()
        {
            if (followIndex == 0 || followIndex == points.Length - 1)
            {
                points = followLine.Points.ToArray();
                
                if (Vector2.Distance(followLine.Points[0], transform.position) >
                    Vector2.Distance(followLine.Points[followLine.Points.Count - 1], transform.position))
                {
                    followIndex = followLine.Points.Count - 1;
                }
                else
                {
                    followIndex = 0;
                }
            }
        }

        public void AddLineData(LineData lineData)
        {
            followLine += lineData;
        }

        public void RemoveLineData(LineData lineData)
        {
            followLine -= lineData;
        }

        /// <summary>
        /// 检查正在运行上的line，并加train的transform加入到line的队列中，用于撤销line的时候等待消除
        /// </summary>
        private GameObject line;
        private bool firstCast = true;
        private void CheckLine()
        {
            RaycastHit[] hits = Physics.RaycastAll(transform.position + Vector3.back, Vector3.forward,
                Mathf.Infinity, LayerTools.LineLayerMask);
            
            foreach (var hit in hits)
            {
                if (Line.CompareLineColor(hit.transform.gameObject.GetInstanceID(), Color))
                {
                    GameObject go = hit.transform.gameObject;
                    if (!go.Equals(line))
                    {
                        if (line != null)
                        {
                            Line.RunWayLine(line.GetHashCode(), transform);
                        }
                        line = go;
                        Line.LineInvisible(go.GetHashCode(), transform);
                        return;

                    }
                }
            }
        }

        private int lastStation = 0;
        
        private void CheckStation()
        {
            lineRay = new Ray(transform.position + Vector3.back, Vector3.forward);
            if (Physics.Raycast(lineRay, out var hitInfo, Mathf.Infinity, LayerTools.NodeLayerMask))
            {
                if (lastStation == hitInfo.transform.gameObject.GetInstanceID())
                {
                    return;
                }
                
                if ((passengerOff != null && passengerOff.Running) || (passengerOn != null && passengerOn.Running))
                {
                    return;
                }

                lastStation = hitInfo.transform.gameObject.GetInstanceID();
                
                RunTask.Pause();
                
                passengerOff = new Task(GetOffPassenger(hitInfo.transform.gameObject.GetInstanceID()));
                
                passengerOff.Finished += delegate(bool manual)
                {
                    if (line == null)
                    {
                        CheckLine();
                    }
                    StationGeneration.Instance.Passenger.TakePassenger(hitInfo.transform.gameObject.GetInstanceID(), line.GetInstanceID(), RemainCapacity(), 
                        delegate(Dictionary<DrawMeshTools.MeshType, int> passengers)
                    {
                        passengerOn = new Task(GeneratePassenger(passengers));
                        passengerOn.Finished += delegate(bool b)
                        {
                            CheckLine();
                            
                            if (followLine.Points == null)
                            {
                                followLine = Line.GetLineFollowData(line.GetInstanceID());
                            }
                            else
                            {
                                var temp =
                                    StationGeneration.Instance.GetNextLineData(lastStation, line.GetInstanceID(), Line.GetLineColor(line.GetInstanceID()));
                                if(temp != null)
                                    followLine = temp;
                            }
                            RunTask.Continue();
                        };
                    });
                };
            }
        }

        private int RemainCapacity()
        {
            int capacity = 0;
            foreach (var model in models)
            {
                if (!model.gameObject.activeSelf)
                {
                    capacity++;
                }
            }

            return capacity;
        }
        
        private IEnumerator GeneratePassenger(Dictionary<DrawMeshTools.MeshType, int> passengers)
        {
            int sum = 0;
            foreach (var VARIABLE in passengers.Values)
            {
                sum += VARIABLE;
            }
            foreach (var type in passengers.Keys)
            {
                int num = passengers[type];
                while (num > 0)
                {
                    foreach (var model in models)
                    {
                        if (!model.gameObject.activeSelf)
                        {
                            model.gameObject.SetActive(true);
                            model.Type = type;
                            num--;
                            break;
                        }
                    }
                    yield return new WaitForSeconds(TimeScale.PASSENGER_ON_TIME);
                }
            }
        }

        private IEnumerator GetOffPassenger(int stationId)
        {
            Transform parent = null;

            List<DrawMeshTools.MeshType> type = StationGeneration.Instance.GetReachable(stationId,
                delegate(Transform t)
                {
                    parent = t;
                });
            if (line == null)
            {
                yield break;
            }
            Dictionary<DrawMeshTools.MeshType, List<int>> onLineType = StationGeneration.Instance.GetStationNetwork(stationId, line.GetInstanceID(), Line.GetLineColor(line.GetInstanceID()));  
      
            foreach (var model in models)
            {
                if (model.gameObject.activeSelf && model.Type == type[0])
                {
                    model.gameObject.SetActive(false);
                    GameManager.Instance.PassengerArrival(); //乘客到站获得奖励
                    yield return new WaitForSeconds(TimeScale.PASSENGER_OFF_TIME);
                }else if (model.gameObject.activeSelf && !onLineType.ContainsKey(model.Type) &&
                          type.Contains(model.Type))
                {
                    PassengerGeneration.Instance.Generate(parent, stationId, model.Type);
                    model.gameObject.SetActive(false);
                    yield return new WaitForSeconds(TimeScale.PASSENGER_OFF_TIME);
                }
            }
        }

        private void OnDrawGizmos()
        {
            Vector3 origin;
            for (int angle = 0; angle < 360; angle += 20)
            {
                origin = transform.position +
                         new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), Mathf.Sin(angle * Mathf.Deg2Rad)) * height / 2;
                Debug.DrawRay(origin, Vector3.forward, Color.green);
            }
        }
    }
}
